package org.sradon.mySpring.context;

import org.apache.commons.beanutils.BeanUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.sradon.mySpring.model.DiExecutor;
import org.sradon.mySpring.model.IocExecutor;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Spring容器实现类
 */
public class ClassPathXmlApplicationContext implements ApplicationContext {
    /**
     * Spring容器 Map集合
     * - key: 默认为全限定名, 如果IOC注解有值, 则用值作为key
     * - value: 保存Bean对象引用
     */
    private final Map<String, Object> beans = new ConcurrentHashMap<>();

    /**
     * Bean定义列表
     * - BeanDefine对象: 保存了Bean的接口、父类、字节码对象、在容器中的Key等属性
     * > 用于解决注入的类型是接口, 但是在容器中的是实现类的情况, 需要去BeanDefine中去匹配有哪些对象实现了这个需要注入的接口
     */
    private final List<BeanDefine> defines = Collections.synchronizedList(new ArrayList<>());

    /**
     * 有参构造 - 创建Spring容器
     * @param resourceName 配置文件名称
     */
    public ClassPathXmlApplicationContext(String resourceName) {
        long startTime = System.currentTimeMillis();
        try {
            // dom4j读取xml文件
            SAXReader saxReader = new SAXReader();
            // 获取输入流
            InputStream is = this.getClass()
                    .getClassLoader()
                    .getResourceAsStream(resourceName);
            if (is == null) throw new RuntimeException("[ERR] 配置文件不存在");
            // 读取xml文件
            Document doc = saxReader.read(is);

            // IOC
            IOCbyAnno(doc);
            IOCbyXML(doc);

            // DI
            DIbyAnno();
            DIbyXML(doc);

            // 完成初始化
            System.out.println(
                    "[SUCCESS] SpringContext 初始化完成, 启动时间: " + (System.currentTimeMillis() - startTime) + "ms"
            );
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("[ERR] 创建Spring容器失败");
        }
    }


    /**
     * IOC - 控制反转, 把对象的创建和管理权交给Spring
     * 创建对象并放入Spring容器
     * @param doc xml配置文件
     */
    private void IOCbyAnno(Document doc) {
        // 扫描包列表
        List<String> packages = new ArrayList<>();

        try {
            // 1. 获取component-scan标签
            List<Node> beanList = doc.selectNodes("//component-scan");

            // 2. 获取标签下的包路径
            for (Node node : beanList)
                // 将所有扫描包放入扫描包列表
                packages = Arrays.asList(node.valueOf("@base-package")
                        // 移除多余的空格
                        .replace(" ", "").split(","));

            // 3. 依次加载所有包下的类, 查看是否需要注入
            for (String packageName : packages) {
                // 准备类加载器
                ClassLoader classLoader = this.getClass().getClassLoader();

                // 获取包下的所有文件
                Enumeration<URL> urls = classLoader.getResources(packageName.replaceAll("\\.", "/"));

                // 遍历
                while (urls.hasMoreElements()) {
                    // 获取文件
                    URL url = urls.nextElement();

                    // 获取文件下的所有类
                    if ("file".equals(url.getProtocol())) {
                        // 获取包下的所有文件
                        File dir = new File(url.getFile());
                        // 加载所有打了注解的类
                        IocExecutor.BeanLoader(dir, packageName, classLoader, beans, defines);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("[ERR] IOC失败");
        }

    }

    private void IOCbyXML(Document doc) {
        /// 读取beans标签的IOC版本
        // 把配置文件中的bean标签实例化, 并放入容器中
        try {
            // 1. 获取所有bean标签
            List<Node> beanList = doc.selectNodes("//bean");

            // 2. 处理所有bean标签
            for (Node node : beanList) {
                // 2.1 获取bean的要素id -key, 实例(Class.forName(className)) -value
                // 获取class属性(com.gxa.bean.xxx)
                String className = node.valueOf("@class");
                if (className == null || className.trim().isEmpty())
                    throw new RuntimeException("[ERR] Bean的class属性不能为空");

                // 获取id或name属性(看你配的是啥)
                String id = node.valueOf("@id");
                if (id == null || id.trim().isEmpty()) {    // 没有id 去获取name
                    id = node.valueOf("@name");
                    if (id == null || id.trim().isEmpty())  // name也没有? 我送你个
                        id = className.substring(
                                className.lastIndexOf(".") + 1
                        );
                }

                // 2.2 通过ClassName实例化类
                Object obj = Class.forName(className).newInstance();

                // 3. 将对象放入集合中
                // beans.put(id, obj);
                IocExecutor.putBean(
                        beans,
                        defines,
                        id,
                        obj,
                        obj.getClass()
                );
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }



    /**
     * DI - 依赖注入
     * 在创建对象的过程中把给属性赋值
     */
    private void DIbyAnno() {
        try {
            // 遍历容器中的对象, 对每个对象进行依赖注入
            for (Object bean: beans.values())
                // 依赖注入
                DiExecutor.ValueLoader(bean, beans, defines);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("[ERR] DI失败");
        }
    }

    private void DIbyXML(Document doc) {
        /// 读取beans标签下property标签的DI版本
        // 读取bean标签从容器中拿到对象, 读取property标签给属性赋值
        try {
            // 1. 获取所有bean标签
            List<Node> beanList = doc.selectNodes("//bean");

            // 2. 遍历bean标签
            for (Node node : beanList) {
                // 获取class属性(com.gxa.bean.xxx)
                String className = node.valueOf("@class");

                // 获取id或name属性(看你配的是啥)
                String id = node.valueOf("@id");
                if (id == null || id.trim().isEmpty()) {    // 没有id 去获取name
                    id = node.valueOf("@name");
                    if (id == null || id.trim().isEmpty())  // name也没有? 我送你个
                        id = className.substring(
                                className.lastIndexOf(".") + 1
                        );
                }

                // 根据标签id/name从容器中获取对象
                Object obj = beans.get(id);

                // 获取每一个bean标签下的所有properties标签
                Element element = (Element) node;
                List<Element> propertiesList = element.elements("property");

                // 处理每一个Element标签
                for (Element properties : propertiesList) {
                    String propertyName = properties.valueOf("@name");
                    if (propertyName==null || propertyName.trim().isEmpty())
                        throw new RuntimeException("[ERR] Bean的property的name属性不能为空");

                    String propertyValue = properties.valueOf("@ref");
                    if (propertyValue != null && !propertyValue.trim().isEmpty()) {
                        // 通过ref去容器中获取对象
                        Object refBean = beans.get(propertyValue);
                        if (refBean == null) throw new RuntimeException("[ERR] 找不到需要注入的ref");
                        // 将该对象注入到对象中
                        BeanUtils.setProperty(obj, propertyName, refBean);
                    } else {
                        propertyValue = properties.valueOf("@value");
                        if (propertyValue == null || propertyValue.trim().isEmpty())
                            throw new RuntimeException("[ERR] Bean的property的value属性不能为空");
                        // 对属性进行赋值
                        BeanUtils.copyProperty(obj, propertyName, propertyValue);
                    }
                }

            }
        } catch (Exception e) {
            throw new RuntimeException("[ERR] DI失败");
        }
    }

    /**
     * 从容器(集合)中根据名字获取bean对象
     * @param name
     * @return
     */
    @Override
    public Object getBean(String name) {
        Object bean = beans.get(name);
        if (bean == null)
            throw new RuntimeException("[ERR] Bean {" + name + "} NOT EXIST");
        return bean;
    }

    /**
     * 从容器(集合)中根据类型获取bean对象
     * @param type
     * @return
     */
    @Override
    public Object getBean(Class<?> type) {
        // 由于容器直接存的全限定名, 所以基本上是拿得到的
        Object bean = beans.get(type.getName());
        // 但是如果存的是自定义名(IOC注解上自定义bean名), 就需要去定义信息对象列表中去找了
        if (bean == null) {
            // System.out.println("FIND IN Defines");
            for (BeanDefine define : defines) {
                if (define.getClazz().equals(type)) {
                    bean = beans.get(define.getContextKey());
                    return bean;
                }
            }
        }
        return bean;
    }

    /**
     * 列出容器中所有的bean对象
     */
    @Override
    public void listBeans() {
        for (Object bean: beans.values())
            System.out.println(bean);
    }

    @Override
    public void listDefines() {
        for (BeanDefine define: defines)
            System.out.println(define);
    }
}
